// 队列中的任务
type QueueTask = {
  id: number, // 任务id
  fn: () => Promise<any>, // 任务函数
  resolve: (value?: any) => void, // 任务完成的回调
  reject: (reason?: any) => void // 任务失败的回调
};
type SingleQueueOptions = {
  name: string,
  max?: number,
}

// 队列
const queue: QueueTask[] = [];

// 标识是否正在处理任务
let isProcessing = false;

// 当前任务id
let currentTaskId = 0;

/**
 * 用于限制并发执行的装饰器
 * @param {object} config - 配置对象
 * @param {string} config.name - 队列名称
 * @param {number} config.max - 最大并发数，默认为1
 */
export default function singleQueue({ name, max = 1 }: SingleQueueOptions) {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    // 保存原方法
    const originalMethod = descriptor.value;
    // 生成一个新方法
    descriptor.value = async function (...args: any[]) {
      // 生成一个任务id
      currentTaskId++;
      const taskId = currentTaskId;
      // 创建一个任务对象
      const task: QueueTask = {
        id: taskId,
        fn: async () => {
          // 执行原方法
          const result = await originalMethod.apply(this, args);
          return result;
        },
        resolve: () => {},
        reject: () => {}
      };
      // 创建Promise对象，以便在外部能够等待任务完成
      const promise = new Promise((resolve, reject) => {
        task.resolve = resolve;
        task.reject = reject;
      });
      // 将任务加入队列
      queue.push(task);
      // 执行队列
      if (!isProcessing) {
        isProcessing = true;
        while (queue.length > 0) {
          // 取出队列中的任务
          const tasks = queue.splice(0, max);
          // 为每个任务创建一个Promise对象
          const promises = tasks.map((task) => task.fn());
          // 并发执行任务
          const results = await Promise.all(promises);
          // 将结果返回给任务的回调函数
          results.forEach((result, index) => {
            const task = tasks[index];
            task.resolve(result);
          });
          // 如果队列已经为空，就将isProcessing置为false并退出循环
          if (queue.length === 0) {
            isProcessing = false;
            break;
          }
        }
      }
      // 返回Promise对象
      return promise;
    };
  };
}
